OR-Tools CP-SAT 搜索参数
所有参数:
https://github.com/google/or-tools/blob/stable/ortools/sat/sat_parameters.proto
很多参数都要在了解背后的搜索算法后,才知道含义
输出所有参数及其默认值:1
2
3
4
5attributes = [attr for attr in dir(solver.parameters) if not attr.startswith('__')]
for attr in attributes:
if attr == "Extensions":
continue
print("{} = {}".format(attr, eval("solver.parameters.{}".format(attr))))
下面是参数类型及一些例子:
Branching and polarity 搜索算法相关参数
1 | enum VariableOrder { |
Conflict analysis 冲突分析相关参数
1 | enum ConflictMinimizationAlgorithm { |
Clause database management 子句数据管理
1 | # 触发清理,当可删除子句数量达到下面的配置 |
Variable and clause activities 变量子句活动设置
当每个冲突找到的时候,变量的活动开始增加1
variable_activity_decay = 0.8
Restart 算法重启相关配置
主要是配置在什么情况会重启算法1
2
3
4
5
6
7
8
9
10
11
12
13enum RestartAlgorithm {
NO_RESTART = 0;
LUBY_RESTART = 1;
DL_MOVING_AVERAGE_RESTART = 2;
LBD_MOVING_AVERAGE_RESTART = 3;
FIXED_RESTART = 4;
}
default_restart_algorithms = "LUBY_RESTART,LBD_MOVING_AVERAGE_RESTART,DL_MOVING_AVERAGE_RESTART";
Limits 搜索限制相关配置
1 | # 最大搜索时间,默认是不限制 |
Other 其他参数
1 | # 求解器随机数种子,可以考虑改变 |
Presolve 阶段相关参数
主要是在搜索前,对model做一些处理,有多种presolve策略(对变量、约束),以加速后续的搜索
Presolve参数过程可以参考:How the CP-SAT solver works
这个阶段对问题规模有一定要求,在较大规模问题下,开启这个应该有加速效果,在较小规模下开启应该会增加时间1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 边界变量消除,变量x,not(x)出现的次数小于这个参数
presolve_bve_threshold = 500
# 是否在presolve阶段使用BVA (Bounded Variable Addition)
presolve_use_bva = 72
# presolve 阶段,最大迭代次数
max_presolve_iterations = 3
# 是否在搜索前使用presolve,默认为True
cp_model_presolve = true
# 高级用法,其实有两套presolve代码,默认是一种
cp_model_postsolve_with_full_solver = false
# 如果是正值,在多种presolve策略后,会尝试停止,方便debug presolve阶段
cp_model_max_num_presolve_operations = 0
还有一些其他的参数,可设置参数很多...
设置cp_model_presolve = false后,速度提升比较明显,不会等待最长超时时间
在n_queens上试验,n=11, 相较开启presolve, 有1s左右的时间提升
Max-sat 参数
1 | # 是否使用一些提示/暗示来找到一个较好的初始解 |
Constraint programming 约束规划参数
1 | # 代表考虑的约束类型,匹配系统中应该设置为2 |
源码编译 & cython相关
环境:ubuntu 18.04 & Python 3.6
调用第三方求解器
OR-Tools中内置一些求解器,如MP Solver和CP-SAT solver。
但是OR-Tools实际上提供的是统一的求解器接口,内部连接的具体求解器我们可以配置,支持以下:
- SCIP 默认整合,无需手动安装
- Gurobi
- GLKP (Linux and MacOS only)
- CPLEX: IBM开发的一个商用求解器
调用第三方求解器时,需要单独安装第三方求解器,同时or-tools需要源码安装。
安装流程可以参考官方文档https://developers.google.com/optimization/install/python/source_linux中Build third parties,以及相关资料中的文档。
下载stable分支源码后,执行make third_party
报错如下:
移除makefiles/Makefile.third_party.unix.mk 210,283行-v参数,-v应该是输出,可能不支持了,移除应该不影响。
也可能是cmake版本问题.
第三方求解器源码编译时间较长(只是编译默认开源求解器scip、CBC等,添加其他可能时间更长)。
编译成功:
可行性较高。可以在不用修改原有代码的情况下,使用各种第三方求解器。
cython相关
1、源码编译or-tools1
make cc
编译成功:
编译后的库文件:
2、编写增加约束cython代码,xx.pyx文件
定义包装器,用来桥接Python解释器到C++代码
需要对c++代码,函数有一定了解
用cython代码写添加约束的逻辑,生成函数
类似如下的函数。
1 | def MakeMatrixConstraint(solver, coefficients, lin_expr, double[:] lb, double[:] ub): |
3、编写setup.py函数,构建扩展模块1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [
Extension('sample',
['sample.pyx'],
libraries=['sample'],
library_dirs=['.'])]
setup(
name = 'Sample extension module',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)
然后就可以在python代码中使用了。
可以尝试cython的方式,但是感觉比较麻烦。
其实可以考虑整数规划整体用C++实现,然后使用C++ grpc开放端口,供线上服务调用。
Tips
1. 减少变量数量 & 减少整数变量的域
2. 首选bool型而不是整数变量/约束
其实从源码来看差不多,bool型变量也是使用的IntVar,只是域是(0, 1)
其实定义bool变量是,可以直接使用IntVal(0, 1),但要考虑有些方法只针对bool型变量
1 | def NewBoolVar(self, name): |
3. 如何只寻找可行方案1
solver.SolveWithSolutionCallback()
SearchForAllSolutions 有存储过程
4. 查看模型的一些信息1
print(model.ModelStats())
5. Parallelization(并行化)
可以使用多个线程并行运行求解器求解
1 | solver = cp_model.CpSolver() |
应该是前6个线程使用前6个搜索算法,其余的都是使用LNS,可能参数不同或者随机种子不同
- Default SAT Search
- Fixed search or LP Branching
- PSEUDO_COST_SEARCH (follow last best solution when branching)
- No linear relaxation (good for big models where CP-SAT propagation is enough)
- Max linear relaxation (gives good lower bounds)
- Core Based search
- LNS for remaining workers
搜索是独立的,存在解重复,可以尝试通过下面这个参数避免:1
interleave_search = True
6. 日志开启
程序崩溃,或者搜索没有结果时,可以查看日志。
1 | log_search_progress = True |
可设置其他相关日志参数
和系统整合是,要考虑日志的兼容性
7. Solution hint / Warm start(解决方案提示/热启动)
如果我们在求解过程中加入一些变量的提示则可能加快求解速度。1
2
3
4
5
6
7
8
9
10
11
12num_vals = 3
x = model.NewIntVar(0, num_vals - 1, 'x')
y = model.NewIntVar(0, num_vals - 1, 'y')
z = model.NewIntVar(0, num_vals - 1, 'z')
model.Add(x != y)
model.Maximize(x + 2 * y + 3 * z)
# Solution hinting: x <- 1, y <- 2
model.AddHint(x, 1)
model.AddHint(y, 2)
8. 单向约束
Implications方法是一种单向的约束操作:a => b (a,b均为布尔型变量) ,a为True则b也为True,反之则不成立1
model.AddImplication(a, b)
9. 推荐使用字典来存储多索引变量
Storing Multi-index variables,便于理解
10. 强制所有变量都不相同约束1
model.AddAddAllDifferent(var_arr)
11. 元素约束1
2
3# target == var_arr[index]
model.AddElement(index, var_arr, target)
12. 优化目标,多目标优化
可以使用带权重的优化目标,比如5 a + 2 b -c,权重代表了优化目标的优先级顺序,权重越大则优先级越高。在使用最大化(最小化)优化时,希望其中的一个目标最小化(最大化),可以给该目标赋一个负值权重
此时尽量不要使用SearchForAllSolutions方法来搜索所有的解
1 | model.Minimize(5*a+2*b-c) |
13. Domain
在创建多个变量时,使用Domain应该比单独创建要快
1 | domain = cp_model.Domain(0, 10) |
相关资料
https://github.com/google/or-tools/blob/stable/ortools/sat/doc/model.md
https://www.jianshu.com/p/da3bf1b47681